Skip to content

fix(analytics): count pageviews via GoatCounter API from an edge function - #144

Merged
roschaefer merged 1 commit into
mainfrom
goatcounter-with-api-key
Oct 1, 2026
Merged

roschaefer merged 1 commit into
mainfrom
goatcounter-with-api-key

Conversation

@roschaefer

Copy link
Copy Markdown
Owner

Visits to roschaefer.de stopped showing up in GoatCounter. The /gc/count Netlify rewrite made every hit arrive from Netlify's AWS egress IPs, and GoatCounter takes the connecting proxy as the client IP and flags AWS ranges as bots (isbot BotRangeAWS). Those hits were stored as bots and never counted, while the endpoint still answered 200 with a GIF.

The edge function records the hit through /api/v0/count instead, passing the visitor's own IP and user agent so bot detection and unique-visitor sessions work again. It keeps the first-party /gc/count path, so ad blockers still don't block the beacon; loading GoatCounter directly was the simpler alternative but loses that.

Requires a GoatCounter API token with the "Record pageviews" permission in the GOATCOUNTER_API_TOKEN Netlify environment variable.

Verified with pnpm check:quick; not yet exercised on a Netlify deploy.

…tion

Visits to roschaefer.de stopped showing up in GoatCounter. The /gc/count
Netlify rewrite made every hit arrive from Netlify's AWS egress IPs, and
GoatCounter takes the connecting proxy as the client IP and flags AWS
ranges as bots (isbot BotRangeAWS). Those hits were stored as bots and
never counted, while the endpoint still answered 200 with a GIF.

The edge function records the hit through /api/v0/count instead, passing
the visitor's own IP and user agent so bot detection and unique-visitor
sessions work again. It keeps the first-party /gc/count path, so ad
blockers still don't block the beacon; loading GoatCounter directly was
the simpler alternative but loses that.

Requires a GoatCounter API token with the "Record pageviews" permission
in the GOATCOUNTER_API_TOKEN Netlify environment variable.

Verified with pnpm check:quick; not yet exercised on a Netlify deploy.
@netlify

netlify Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for roschaefer ready!

Name Link
🔨 Latest commit 7b41ad9
🔍 Latest deploy log https://app.netlify.com/projects/roschaefer/deploys/6abe92a5738c550008cf230a
😎 Deploy Preview https://deploy-preview-144--roschaefer.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: c7d14bca-df14-4d3b-aa39-e5cf02065a96

📥 Commits

Reviewing files that changed from the base of the PR and between 507571f and 7b41ad9.

📒 Files selected for processing (3)
  • netlify.toml
  • netlify/edge-functions/goatcounter.ts
  • src/lib/utils/goatcounter-edge-function.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The /gc/count redirect was replaced with a Netlify edge-function mapping. The function parses tracking data, conditionally sends a hit to GoatCounter, and returns a no-store GIF response. The existing /gc/count.js redirect remains unchanged.

Changes

GoatCounter tracking

Layer / File(s) Summary
Parse and send tracking hits
netlify/edge-functions/goatcounter.ts, src/lib/utils/goatcounter-edge-function.test.ts
The edge function maps request parameters and headers to a hit. When the API token and page path are present, it sends the hit to GoatCounter. It returns a no-store GIF and logs missing-token or send errors. Tests cover hit fields, API authorization, and response behavior.
Route requests to the edge function
netlify.toml
The /gc/count redirect is removed and the route is mapped to the goatcounter edge function. The /gc/count.js redirect is unchanged.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant NetlifyGoatcounterEdgeFunction
  participant GoatCounterAPI
  Visitor->>NetlifyGoatcounterEdgeFunction: Request /gc/count with tracking data
  NetlifyGoatcounterEdgeFunction->>GoatCounterAPI: POST hit when token and page path are present
  NetlifyGoatcounterEdgeFunction->>Visitor: Return no-store GIF
Loading

Merge Risk: ⚪ Minimal · up to 7b41a

The change preserves first-party tracking while forwarding visitor details through the GoatCounter API. No actionable merge-blocking issue was found. Configure the required API token and confirm pageviews on a Netlify deploy.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 7b41a

The new credentialed relay is limited to one analytics site, with a fixed destination and a server-side token. Public tracking submissions were already possible. No increased access to other assets was established, but deployed token permissions, visitor identity handling, and background delivery remain unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The directly reachable action is a tracking-write attempt to one hard-coded GoatCounter site, with corresponding edge and API work. Caller input does not select another site, service, data store, or credential. Broader privileges on the deployed token are not established by the repository.

Security Findings and Attack Paths

  • observed — An unauthenticated caller supplying p can trigger a credentialed POST when the token is configured, including caller-selected event and bot values. The prior rewrite already exposed public hit submission. Without verified upstream enforcement semantics, these facts do not establish that this PR introduces a bot-control bypass or materially greater analytics-write authority.

Trust Boundaries and Controls

  • observed — The edge handler mediates between untrusted beacon data and a server-authenticated external API. It fixes the destination and sources IP from the runtime context rather than caller parameters. Other tracking fields remain caller-controlled, and the handler contains no local authentication or rate-limiting control.

Resilience and Maintainability Implications

  • inferred — Separating the GIF response from API completion contains downstream failures at the beacon interface, but a successful beacon response cannot demonstrate that the credentialed integration is functioning. Local tests establish payload construction and suppressed-send behavior, not production identity, interruption, or persistence guarantees.

Hardening Proposals

  • proposed — Provision a token restricted to the intended site's Record pageviews permission and intended deployment environments. Before rollout, verify runtime-derived visitor identity and post-response delivery with an actual deployment; assess upstream abuse controls before deciding whether the public relay needs additional limits.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: counting pageviews through the GoatCounter API from a Netlify edge function.
Description check ✅ Passed The description directly explains the analytics failure, the edge-function solution, required configuration, and validation status.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Comment @coderabbitai help to get the list of available commands.

@roschaefer
roschaefer marked this pull request as ready for review October 1, 2026 17:11
@roschaefer
roschaefer merged commit 729dcbd into main Oct 1, 2026
8 checks passed
@roschaefer
roschaefer deleted the goatcounter-with-api-key branch October 1, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant